﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace ChatBox.UI
{
  /// <summary>
  /// An image that supports multiple sizes depending on its own rendering size.
  /// </summary>
  /// <remarks>
  /// Extends the standard <see cref="Image"/> control with the functionality to 
  /// load all the available frames in its <see cref="Image.Source"/> and render the one
  /// that best matches the current rendering size of the control.
  /// 
  /// For example, if the Source is a TIFF file containing frames of sizes 24x24, 48x48 and 128x128
  /// and the image is rendered at size 40x40, the frame with resolution 48x48 is used.
  /// The same control with the same source rendered at 24x24 would use the 24x24 frame.
  /// 
  /// <para>Written by Isak Savo - isak.savo@gmail.com, (c) 2011-2012. Licensed under the Code Project  </para>
  /// </remarks>
  public class MultiSizeImage : Image
  {
    #region Constructors

    public MultiSizeImage()
      : base()
    {
    }

    #endregion

    #region Protected Methods

    /// <summary>
    /// Renders the contents of the image
    /// </summary>
    /// <param name="dc">An instance of <see cref="T:System.Windows.Media.DrawingContext"/> used to render the control.</param>
    protected override void OnRender( DrawingContext dc )
    {
      if ( this.Source != null )
      {
        Double ourSize = this.RenderSize.Width * this.RenderSize.Height;

        dc.DrawImage( ( _availableFrames.FirstOrDefault( x => x.PixelWidth * x.PixelHeight >= ourSize ) ?? _availableFrames.LastOrDefault() ?? this.Source ), new Rect( new Point(), this.RenderSize ) );
      }
      else
      {
        base.OnRender( dc );
      }
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Scans the ImageSource for available frames and stores them as individual
    /// bitmap sources. This is done once, when the <see cref="Image.Source"/>
    /// property is set (or changed).
    /// </summary>
    private void UpdateAvailableFrames()
    {
      _availableFrames.Clear();

      BitmapFrame frame = this.Source as BitmapFrame;

      if ( frame != null )
      {
        BitmapDecoder decoder = frame.Decoder;

        if ( decoder != null && decoder.Frames != null )
        {
          _availableFrames.AddRange( decoder.Frames.GroupBy( x => x.PixelWidth * x.PixelHeight ).OrderBy( x => x.Key ).Select( x => x.OrderByDescending( MultiSizeImage.GetFramePixelDepth ).First() ) );
        }
      }
    }

    #endregion

    #region Private Variables

    /// <summary>
    /// List containing one frame of every size available in the original file.
    /// The frame stored in this list is the one with the highest pixel depth
    /// for that size.
    /// </summary>
    private readonly List<BitmapSource> _availableFrames = new List<BitmapSource>();

    #endregion

    #region Static Constructor

    static MultiSizeImage()
    {
      Image.SourceProperty.OverrideMetadata( typeof( MultiSizeImage ), new FrameworkPropertyMetadata( MultiSizeImage.OnSourceChanged ) );
    }

    #endregion

    #region Private Static Methods

    private static void OnSourceChanged( DependencyObject _sender, DependencyPropertyChangedEventArgs _e )
    {
      MultiSizeImage img = ( MultiSizeImage )_sender;

      img.UpdateAvailableFrames();
    }

    private static readonly Guid IcoGuid = new Guid( "{a3a860c4-338f-4c17-919a-fba4b5628f21}" );

    /// <summary>
    /// Gets the pixel depth (in bits per pixel, bpp) of the specified frame
    /// </summary>
    /// <param name="_frame">Frame to get BPP for.</param>
    /// <returns>Number of bits per pixel in the frame.</returns>
    private static Int32 GetFramePixelDepth( BitmapFrame _frame )
    {
      return ( _frame.Decoder.CodecInfo.ContainerFormat == IcoGuid && _frame.Thumbnail != null ) ? _frame.Thumbnail.Format.BitsPerPixel : _frame.Format.BitsPerPixel;
    }

    #endregion
  }
}
